// SPDX-License-Identifier: GPL-2.0-or-later
/**
 * @brief zmodem 模块
 * @version
 * @author ~ 宋炜
 * @date ~ 2023-03-30
 */
#ifndef FQTERM_ZMODEM_H
#define FQTERM_ZMODEM_H

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <QByteArray>
#include <QObject>
#include <QTimer>
#include <QFile>
#include <QStringList>

namespace FQTerm {

/* PARAMETERS
 *
 * The following #defines control the behavior of the Zmodem
 * package.  Note that these may be replaced with variables
 * if you like.  For example, "#define DoInitRZ" may be replaced
 * with "extern int DoInitRz" to use a global variable, or with
 * "#define DoInitRZ (info->doInitRz)" to use a variable you
 * add to the ZModem structure.
 *
 * It is assumed that the compiler is good enough to optimize
 * "if( 0 )" and "if( 1 )" cases.  If not, you may wish to modify
 * the source code to use #ifdef instead.
 */

#define DoInitRZ        1	/* send initial "rz\r" when transmitting */
#define AllowCommand	0	/* allow remote end to execute commands */
#define SendSample      1	/* sender can sample reverse channel */
#define SendAttn        1	/* sender can be interrupted with Attn signal */
#define ResponseTime	10	/* reasonable response time for sender to
                             * respond to requests from receiver */
#define SerialNo        1	/* receiver serial # */
#define MaxNoise        64	/* max "noise" characters before transmission
                             * pauses */
#define MaxErrs         30	/* Max receive errors before cancel */
#define AlwaysSinit     1	/* always send ZSINIT header, even if not
                             * needed, this makes protocol more robust */

#define SendOnly        0	/* compiles smaller version for send only */
#define RcvOnly         0	/* compiles smaller version for receive only */

enum enum_InputState { Idle, Padding, Inhdr, Indata, Finish, Ysend, Yrcv };
enum enum_Protocol { XMODEM, YMODEM, ZMODEM };
enum enum_Streaming { Full, StrWindow, SlidingWindow, Segmented };
enum enum_transferstate { notransfer, transferstart, transferstop };


/* Internal State */
typedef enum zmstate {
	/* receive */
	RStart,      /* sent RINIT, waiting for ZFILE or SINIT */
	RSinitWait,  /* got SINIT, waiting for data */
	RFileName,   /* got ZFILE, waiting for filename & info */
	RCrc,        /* got filename, want crc too */
	RFile,       /* got filename, ready to read */
	RData,       /* reading data */
	RDataErr,    /* encountered error, ignoring input */
	RFinish,     /* sent ZFIN, waiting for 'OO' */

	/* transmit */
	TStart,      /* waiting for INIT frame from other end */
	TInit,       /* received INIT, sent INIT, waiting for ZACK */
	FileWait,   /* sent file header, waiting for ZRPOS */
	CrcWait,    /* sent file crc, waiting for ZRPOS */
	Sending,    /* sending data subpackets, ready for int */
	SendWait,  /* waiting for ZACK */
	SendDone,   /* file finished, need to send EOF */
	SendEof,    /* sent EOF, waiting for ZACK */
	TFinish,    /* sent ZFIN, waiting for ZFIN */

	/* general */
	CommandData,  /* waiting for command data */
	CommandWait,  /* waiting for command to execute */
	StderrData,  /* waiting for stderr data */
	Done,

	/* x/ymodem transmit */
	YTStart,  /* waiting for 'G', 'C' or NAK */
	YTFile,  /* sent filename, waiting for ACK */
	YTDataWait,  /* ready to send data, waiting for 'C' */
	YTData,  /* sent data, waiting for ACK */
	YTEOF,  /* sent eof, waiting for ACK */
	YTFin,  /* sent null filename, waiting for ACK */

	/* x/ymodem receive */
	YRStart,  /* sent 'C', waiting for filename */
	YRDataWait,  /* received filename, waiting for data */
	YRData,  /* receiving filename or data */
	YREOF /* received first EOT, waiting for 2nd */
} ZMState;

typedef struct {
	uchar           f0, f1, f2, f3;       /* file flags */

	int             ifd;            /* input fd, for use by caller's routines */
	int             ofd;            /* output fd, for use by caller's routines */
	int             zrinitflags;    /* receiver capabilities, see below */
	int             zsinitflags;    /* sender capabilities, see below */
	int             timeout;        /* timeout value, in seconds */
	int             bufsize;        /* receive buffer size, bytes */
	int             packetsize;     /* preferred transmit packet size */
	int             windowsize;     /* max window size */
	/* file attributes: read-only */
	int             filesRem, bytesRem;
	int             len, mode, fileType;    /* file flags */
	int             crc32;          /* use 32-bit crc */
	int             pktLen;         /* length of this packet */
	int             DataType;       /* input data type */
	int             PacketType;     /* type of this packet */
	int             rcvlen;
	int             chrCount;       /* chars received in current header/buffer */
	int             crcCount;       /* crc characters remaining at end of buffer */
	int             canCount;       /* how many CAN chars received? */
	int             noiseCount;     /* how many noise chars received? */
	int             errorFlush;     /* ignore incoming data because of error */
	int             ylen, bufp;     /* len,location of last Ymodem packet */
	int             fileEof;        /* file eof reached */
	int             packetCount;    /* # packets received */
	int             errCount;       /* how many data errors? */
	int             timeoutCount;   /* how many times timed out? */
	int             windowCount;    /* how much data sent in current window */
	int             atSign;         /* last char was '@' */
	int             lastCR;         /* last char was CR */
	int             escCtrl;        /* other end requests ctrl chars be escaped */
	int             escHibit;       /* other end requests hi bit be escaped */
	int             escape;         /* next character is escaped */
	int             interrupt;      /* received attention signal */
	int             waitflag;       /* next send should wait */
	ulong           date;           /* file date */
	ulong           crc;            /* crc of incoming header/data */
	/* From here down, internal to Zmodem package */
	char            * filename;     /* filename */
	char            * rfilename;    /* remote filename */
	uchar           * buffer;       /* data buffer */
	FILE            * file;         /* file being transferred */
	char            * attn;         /* attention string, see below */

	ulong           offset;         // file offset
	ulong           lastOffset;     // last acknowledged offset
	ulong           zrposOffset;    // last offset specified w/zrpos

	ZMState         state;          // protocol internal state
	enum_InputState InputState;     // enum enum_InputState {Idle, Padding, Inhdr, Indata, Finish, Ysend, Yrcv} InputState ;
	enum_Protocol   Protocol;       // enum enum_Protocol {XMODEM, YMODEM, ZMODEM} Protocol
	enum_Streaming  Streaming;      // num enum_Streaming {Full, StrWindow, SlidingWindow, Segmented} Streaming ;

	uchar           hdrData[9];     // header type and data
	uchar           fileFlags[4];   // file xfer flags
} ZModem;

/* ZRINIT flags.  Describe receiver capabilities */

#define CANFDX	1	/* Rx is Full duplex */
#define CANOVIO	2	/* Rx can overlap I/O */
#define CANBRK	4	/* Rx can send a break */
#define CANCRY	010	/* Rx can decrypt */
#define CANLZW	020	/* Rx can uncompress */
#define CANFC32	040	/* Rx can use 32-bit crc */
#define ESCCTL	0100	/* Rx needs control chars escaped */
#define ESC8	0200	/* Rx needs 8th bit escaped. */

/* ZSINIT flags.  Describe sender capabilities */

#define TESCCTL	0100	/* Tx needs control chars escaped */
#define TESC8	0200	/* Tx needs 8th bit escaped. */


/* ZFILE transfer flags */

/* F0 */
#define ZCBIN	1       /* binary transfer */
#define ZCNL	2       /* convert NL to local eol convention */
#define ZCRESUM	3       /* resume interrupted file xfer, or append to a
			   growing file. */

/* F1 */
#define ZMNEWL	1       /* transfer if source newer or longer */
#define ZMCRC	2       /* transfer if different CRC or length */
#define ZMAPND	3       /* append to existing file, if any */
#define ZMCLOB	4       /* replace existing file */
#define ZMNEW	5       /* transfer if source is newer */
#define ZMDIFF	6       /* transfer if dates or lengths different */
#define ZMPROT	7       /* protect: transfer only if dest doesn't exist */
#define ZMCHNG	8       /* change filename if destination exists */
#define ZMMASK	037     /* mask for above. */
#define ZMSKNOLOC 0200	/* skip if not present at Rx end */

/* F2 */
#define ZTLZW	1       /* lzw compression */
#define ZTRLE	3       /* run-length encoding */

/* F3 */
#define ZCANVHDR 1      /* variable headers ok */
#define ZRWOVR	4       /* byte position for receive window override/256 */
#define ZXSPARS         64      /* encoding for sparse file ops. */

/* ATTN string special characters.  All other characters sent verbose */
#define ATTNBRK         '\335'	/* send break signal */
#define ATTNPSE         '\336'	/* pause for one second */

/* error code definitions [O] means link still open */
#define ZmDone          -1	/* done */
#define ZmErrInt        -2	/* internal error */
#define ZmErrSys        -3	/* system error, see errno */
#define ZmErrNotOpen	-4	/* communication channel not open */
#define ZmErrCantOpen	-5	/* can't open file, see errno [O] */
#define ZmFileTooLong	-6	/* remote filename too long [O] */
#define ZmFileCantWrite	-7	/* could not write file, see errno */
#define ZmDataErr       -8	/* too many data errors */
#define ZmErrInitTo     -10	/* transmitter failed to respond to init req. */
#define ZmErrSequence	-11	/* packet received out of sequence */
#define ZmErrCancel     -12	/* cancelled by remote end */
#define ZmErrRcvTo      -13	/* remote receiver timed out during transfer */
#define ZmErrSndTo      -14	/* remote sender timed out during transfer */
#define ZmErrCmdTo      -15	/* remote command timed out */

/* ZModem character definitions */

#define ZDLE        030     /* zmodem escape is CAN */
#define ZPAD        '*'     /* pad */
#define ZBIN        'A'     /* introduces 16-bit crc binary header */
#define ZHEX        'B'     /* introduces 16-bit crc hex header */
#define ZBIN32      'C'     /* introduces 32-bit crc binary header */
#define ZBINR32     'D'     /* introduces RLE packed binary frame w/32-bit crc */
#define ZVBIN       'a'     /* alternate ZBIN */
#define ZVHEX       'b'     /* alternate ZHEX */
#define ZVBIN32     'c'     /* alternate ZBIN32 */
#define ZVBINR32    'd'     /* alternate ZBINR32 */
#define ZRESC       0177	/* RLE flag/escape character */

/* ZModem header type codes */
#define ZRQINIT     0	/* request receive init */
#define ZRINIT      1	/* receive init */
#define ZSINIT      2	/* send init sequence, define Attn */
#define ZACK        3	/* ACK */
#define ZFILE       4	/* file name, from sender */
#define ZSKIP       5	/* skip file command, from receiver */
#define ZNAK        6	/* last packet was garbled */
#define ZABORT      7	/* abort */
#define ZFIN        8	/* finish session */
#define ZRPOS       9	/* resume file from this position, from receiver */
#define ZDATA       10	/* data packets to follow, from sender */
#define ZEOF        11	/* end of file, from sender */
#define ZFERR       12	/* fatal i/o error, from receiver */
#define ZCRC        13	/* request for file crc, from receiver */
#define ZCHALLENGE  14	/* "send this number back to me", from receiver */
#define ZCOMPL      15	/* request is complete */
#define ZCAN        16	/* other end cancelled with CAN-CAN-CAN-CAN-CAN */
#define ZFREECNT    17	/* request for free bytes on filesystem */
#define ZCOMMAND    18	/* command, from sending program */
#define ZSTDERR     19	/* output this message to stderr */


/* ZDLE escape sequences */
#define ZCRCE	'h'	/* CRC next, frame ends, header follows */
#define ZCRCG	'i'	/* CRC next, frame continues nonstop */
#define ZCRCQ	'j'	/* CRC next, send ZACK, frame continues nonstop */
#define ZCRCW	'k'	/* CRC next, send ZACK, frame ends */
#define ZRUB0	'l'	/* translate to 0177 */
#define ZRUB1	'm'	/* translate to 0377 */

/* ascii definitions */
#define SOH     1	/* ^A */
#define STX     2	/* ^B */
#define EOT     4	/* ^D */
#define ACK     6	/* ^F */
#define DLE     16	/* ^P */
#define XON     17	/* ^Q */
#define XOFF	19	/* ^S */
#define NAK     21	/* ^U */
#define SYN     22	/* ^V */
#define CAN     24	/* ^X */

/* state table entry.  There is one row of the table per
 * possible state.  Each row is a row of all reasonable
 * inputs for this state.  The entries are sorted so that
 * the most common inputs occur first, to reduce search time
 * Unexpected input is reported and ignored, as it might be
 * caused by echo or something.
 *
 * Extra ZRINIT headers are the receiver trying to resync.
 */
class FQTermConfig;
class FQTermZmodem;
class FQTermTelnet;
class FQTermFileDialog;

typedef int(FQTermZmodem:: *ActionFunc)(ZModem*);

typedef struct {
	int type;           /* frame type */
	char statName[ 50 ];
	ActionFunc func;    // int(*func)(ZModem *) ;	transition function
	int IFlush;         /* flag: flush input first */
	int OFlush;         /* flag: flush output first */
	ZMState newstate;   /* new state.  May be overridden by func */
} StateTable;

//class FQTermTelnet;

class FQTermZmodem: public QObject {
	Q_OBJECT
      public:
	FQTermZmodem(FQTermConfig *config, FQTermTelnet *netinterface, int type, int serverEncoding);
	~FQTermZmodem();

	/* zmodem-supplied functions: */
	/**
	 * @brief 初始化上传操作
	 * @param info[ I ]，要初始化的目标信息
	 * @return
	*/
	int ZmodemTInit(ZModem *info);
	int ZmodemTFile(char *file, char *rmtname, uint f0, uint f1, uint f2, uint f3, int filesRem, int bytesRem, ZModem *info);
	/**
	 * @brief 发送文件完成处理
	 * @param info
	 * @return
	 */
	int ZmodemTFinish(ZModem *info);
	int ZmodemAbort(ZModem *info);
	/**
	 * @brief 接收文件初始化
	 * @param info
	 * @return
	 */
	int ZmodemRInit(ZModem *info);
	/**
	 * @brief 处理接收数据入口
	 * @param str
	 * @param len
	 * @param info
	 * @param consumed_bytes
	 * @return
	*/
	int ZmodemRcv(uchar *str, int len, ZModem *info, int &consumed_bytes);
	int ZmodemAttention(ZModem *info);

	int ZmodemReset(ZModem *info);

	int YmodemTInit(ZModem *info);
	int XmodemTInit(ZModem *info);
	int YmodemRInit(ZModem *info);
	int XmodemRInit(ZModem *info);

	ulong FileCrc(char *name);
	const char *sname(ZModem*);
	const char *sname2(ZMState);

	/* caller-supplied functions: */
	int ZXmitChr(uchar c, ZModem *info);
	int ZXmitStr(const uchar *str, int len, ZModem *info);
	void ZIFlush(ZModem *info);
	void ZOFlush(ZModem *info);
	int ZAttn(ZModem *info);
	void ZStatus(int type, int value, const char *status);

	FILE *ZOpenFile(char *name, ulong crc, ZModem *info);
	/**
	* @brief 本地保存文件
	* @param buffer
	* @param len
	* @return
	*/
	int ZWriteFile(uchar *buffer, int len, FILE *, ZModem*);
	int ZCloseFile(ZModem *info);
	void ZFlowControl(int onoff, ZModem *info);

	/* end caller-supplied functions */

	int ZXmitHdr(int type, int format, const uchar data[4], ZModem *info);
	int ZXmitHdrHex(int type, const uchar data[4], ZModem *info);
	int ZXmitHdrBin(int type, const uchar data[4], ZModem *info);
	int ZXmitHdrBin32(int type, const uchar data[4], ZModem *info);
	uchar *putZdle(uchar *ptr, uchar c, ZModem *info);
	uchar *putHex(uchar *ptr, uchar c);

	uchar *ZEnc4(ulong n);
	ulong ZDec4(const uchar buf[4]);

	int YrcvChar(char c, ZModem *info);
	int YrcvTimeout(ZModem *info);
	/**
	* @brief 没有实质的内容
	* @param buffer
	* @param len
	* @param info
	*/
	void ZIdleStr(uchar *buffer, int len, ZModem *info);

	/* LEXICAL BOX: handle characters received from remote end.
	 * These may be header, data or noise.
	 *
	 * This section is a finite state machine for parsing headers
	 * and reading input data.  The info->chrCount field is effectively
	 * the state variable.
	 */

	int FinishChar(char c, ZModem *info);
	int DataChar(uchar c, ZModem *info);
	int HdrChar(uchar c, ZModem *info);
	int IdleChar(uchar c, ZModem *info);

	int YsendChar(char c, ZModem *info);

	int ZPF(ZModem *info);
	int Ignore(ZModem *info);
	int AnswerChallenge(ZModem *info);
	int GotAbort(ZModem *info);
	int GotCancel(ZModem *info);
	int GotCommand(ZModem *info);
	int GotStderr(ZModem *info);
	int RetDone(ZModem *info);
	int GotCommandData(ZModem *info, int crcGood);
	int GotStderrData(ZModem *info, int crcGood);

	int GotFileName(ZModem *info, int crcGood);
	int ResendCrcReq(ZModem *info);
	int GotSinitData(ZModem *info, int crcGood);
	int ResendRpos(ZModem *info);
	/**
     * @brief 处理接收文件数据内容
     * @param info
     * @param crcGood
     * @return
     */
	int GotFileData(ZModem *info, int crcGood);
	int SendRinit(ZModem *info);

	int GotSinit(ZModem *info);
	int GotFile(ZModem *info);
	int GotFin(ZModem *info);
	int GotData(ZModem *info);
	int GotEof(ZModem *info);
	int GotEofDoNothing( ZModem * info );
	int GotFreecnt(ZModem *info);
	int GotFileCrc(ZModem *info);

	int GotRinit(ZModem*);
	int SendZSInit(ZModem*);
	int SendFileCrc(ZModem*);
	int GotSendAck(ZModem*);
	int GotSendDoneAck(ZModem*);
	int GotSendNak(ZModem*);
	int GotSendWaitAck(ZModem*);
	int SkipFile(ZModem*);
	int GotSendPos(ZModem*);

	int requestFile(ZModem *info, ulong crc);
	void parseFileName(ZModem *info, char *fileinfo);
	int fileError(ZModem *info, int type, int data);
	/**
     * @brief 处理接收文件的数据包
     * @param info
     * @return
     */
	int ProcessPacket(ZModem *info);
	int rejectPacket(ZModem *info);
	int acceptPacket(ZModem *info);

	int calcCrc(uchar *str, int len);

	char *strdup(const char *str);

	int SendMoreFileData(ZModem *info);

	uint rcvHex(uint i, char c);

	int ZDataReceived(ZModem *info, int crcGood);
	int dataSetup(ZModem *info);
	/**
     * @brief 处理入口，根据当前的协议内部状态选择状态机进行处理
     * @param info
     * @return
     */
	int ZProtocol(ZModem *info);

	int ZXmitData(int, int, uchar, uchar *data, ZModem*);
	int YXmitData(uchar *, int, ZModem*);

	int YSendFilename(ZModem*);
	int YSendData(ZModem*);
	int YSendFin(ZModem*);

	int sendFilename(ZModem*);
	int SendFileData(ZModem*);
	int ResendEof(ZModem*);

	int OverAndOut(ZModem*);

	int startFileData(ZModem *info);

	void zmodemlog(const char *fmt, ...);

	// interface function
	// all not completed
	void transferTimeOut(void*);
	void upload(char*);
	void uploadNext(char*);
	void transferFinish();
	void xferCancel();

	/* data received from remote, pass it to Zmodem funcs */
	void transferSendData(char *, int);
	void TransferCancel();
      public:
	static StateTable RStartOps[];
	static StateTable RSinitWaitOps[];
	static StateTable RFileNameOps[];
	static StateTable RCrcOps[];
	static StateTable RFileOps[];
	static StateTable RDataOps[];
	static StateTable RFinishOps[];


	static StateTable       TStartOps[];
	static StateTable       TInitOps[];
	static StateTable       FileWaitOps[];
	static StateTable       CrcWaitOps[];
	static StateTable       SendingOps[];
	static StateTable       SendDoneOps[];
	static StateTable       SendWaitOps[];
	static StateTable       SendEofOps[];
	static StateTable       TFinishOps[];

	static StateTable       CommandDataOps[];
	static StateTable       CommandWaitOps[];
	static StateTable       StderrDataOps[];
	static StateTable       DoneOps[];

	static StateTable     * tables[];           // 状态机对象表
	static const char     * hdrnames[];

	bool                    sending;
	uchar                   lastPullByte;

	int                     connectionType;		//  connection type, e.g. telnet or ssh , 0=telnet, 1=ssh
	int                     serverEncodingID;
	int                     zerrno;

	FQTermTelnet          * telnet_;		// 数据通讯通道
	FQTermConfig          * config_;
	QTimer                * zmodemTimer;		// 超时计时器
	FILE                  * zmodemlogfile;		// zmodem日志文件

	enum_transferstate      transferstate;		// 传输状态

	ZModem                    info;			// zmodem 信息
	QStringList               strFileList;
	QStringList::Iterator     itFile;
	QString                   lastDownloadedFilename_;
    FQTermFileDialog        * __p_file_dialog;
	std::atomic< bool >       __m_cooled;
	QTimer                    __m_cool_timer;	// 传输完成冷却时间
      signals:
	void ZmodemState(int, int, const char *);
      private slots:
	void __on_selected_files( bool sw , const QStringList& files);
	void __on_cooled();
      public slots:

	void zmodemCancel();
	int ZmodemTimeout();
};

}  // namespace FQTerm

#endif  // FQTERM_ZMODEM_H
